#include "../../src/util/object.hh"
#include "../framework/unittest.hh"
#include <iostream>
#include <thread>
#include <string>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <memory>

using namespace kratos::object_system;

FIXTURE_BEGIN(test_object_system)

class Dummy {
public:
	Dummy() {}
	Dummy(int, int) {}
	virtual ~Dummy() {
		return;
	}
	void method() const {}
};

class Dummy1 {
public:
	virtual ~Dummy1() {
		return;
	}
};

CASE(TestSpawn1) {
	auto ref = spawn_object(Dummy, 1, 2);
	ASSERT_TRUE(ref);
	ASSERT_TRUE(ref.dispose());
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(0 == get_dispose_object_count());
	ASSERT_TRUE(0 == get_alive_object_count());
}

CASE(TestSpawn2) {
	auto ref = spawn_object(Dummy, 1, 2);
	ASSERT_TRUE(ref);
	ASSERT_TRUE(ref.type_info() != nullptr);
	ref.type_info()->name();
	ref.id();
	ref.thread_id();
	(std::uint64_t)ref;
	(std::string)ref;
	(const std::string)ref;
	ASSERT_TRUE(ref.dispose());
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(ref.type_info() != nullptr);
	ref.type_info()->name();
}

CASE(TestDispose1) {
	auto ref = spawn_object(Dummy, 1, 2);
	auto ref_other = ref;
	ASSERT_TRUE(ref);
	ASSERT_TRUE(ref.dispose());
	ASSERT_TRUE(!ref);
	ASSERT_EXCEPT(*ref);
	ASSERT_EXCEPT(ref->method());
	ASSERT_TRUE(!ref_other);
}

CASE(TestDispose2) {
	auto ref = spawn_object(Dummy, 1, 2);
	auto ref_other = ref;
	(*ref).method();
	ASSERT_TRUE(ref.dispose());
	ASSERT_TRUE(!ref.dispose());
	ASSERT_TRUE(!ref_other.dispose());
}

CASE(TestDispose3) {
	const auto ref = spawn_object(Dummy, 1, 2);
	auto ref_other = ref;
	(*ref).method();
	ASSERT_TRUE(ref.dispose());
	ASSERT_EXCEPT((*ref).method());
	ASSERT_EXCEPT(ref->method());
	ASSERT_TRUE(!ref.dispose());
	ASSERT_TRUE(!ref_other.dispose());
}

CASE(TestGet1) {
	auto ref = spawn_object(Dummy, 1, 2);
	ASSERT_TRUE(ref);
	auto id = ref.id();
	auto ref1 = get_object<Dummy>(ref.id());
	ASSERT_TRUE(ref1);
	ASSERT_TRUE(ref.dispose());
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(!ref1);
	ASSERT_TRUE(!get_object<Dummy>(id));
	ASSERT_TRUE(!get_object<Dummy>(1234567));
}

CASE(TestGet2) {
	auto ref = spawn_object(Dummy, 1, 2);
	ASSERT_TRUE(ref);
	auto ref1 = get_object<Dummy1>(ref.id());
	ASSERT_TRUE(!ref1);
	ref.dispose();
}

CASE(TestKey1) {
	object_set<Dummy> set;
	for (int i = 0; i < 100; i++) {
		set.insert(spawn_object(Dummy));
	}
	for (auto& ref : set) {
		ref.dispose();
	}
}

CASE(TestKey2) {
	object_unordered_set<Dummy> set;
	for (int i = 0; i < 100; i++) {
		set.insert(spawn_object(Dummy));
	}
	for (auto& ref : set) {
		ref.dispose();
	}
}

CASE(TestKey3) {
	object_map<Dummy, int> map;
	for (int i = 0; i < 100; i++) {
		map[spawn_object(Dummy)] = 1;
	}
	for (auto& it : map) {
		it.first.dispose();
	}
}

CASE(TestKey4) {
	object_unordered_map<Dummy, int> map;
	for (int i = 0; i < 100; i++) {
		map[spawn_object(Dummy)] = 1;
	}
	for (auto& it : map) {
		it.first.dispose();
	}
}

CASE(TestMultiThread1) {
	ObjectRef<Dummy> ref = spawn_object(Dummy);
	bool flag = false;
	std::thread t([&]() {
		flag = ref.dispose();
	});
	t.join();
	ASSERT_TRUE(!flag);
	ref.dispose();
}

CASE(TestMultiThread2) {
	ObjectRef<Dummy> ref = spawn_object(Dummy);
	bool flag = false;
	std::thread t([&]() {
		try {
			ref->method();
		}
		catch (...) {
			flag = true;
		}
	});
	t.join();
	ASSERT_TRUE(flag);
	ref.dispose();
}

CASE(TestMultiThread3) {
	ObjectRef<Dummy> ref = spawn_object_multithread(Dummy);
	bool flag = false;
	std::thread t([&]() {
		flag = ref.dispose();
	});
	t.join();
	ASSERT_TRUE(flag);
}

CASE(TestMultiThread4) {
	ObjectRef<Dummy> ref = spawn_object_multithread(Dummy);
	std::thread t([&]() {
		ref->method();
	});
	t.join();
	ref.dispose();
}

CASE(TestMultiThread5) {
	const auto ref = spawn_object(Dummy);
	bool flag = false;
	bool except = false;
	std::thread t([&]() {
		try {
			ref->method();
		}
		catch (...) {
			except = true;
		}
		flag = ref.dispose();
		});
	t.join();
	ASSERT_TRUE(!flag);
	ASSERT_TRUE(except);
	ref.dispose();
}

CASE(TestMultiThread6) {
	ObjectRef<Dummy> ref = spawn_object(Dummy);
	bool flag = false;
	std::thread t([&]() {
		try {
			(*ref).method();
		}
		catch (...) {
			flag = true;
		}
		});
	t.join();
	ASSERT_TRUE(flag);
	ref.dispose();
}

CASE(TestMultiThread7) {
	const auto ref = spawn_object(Dummy);
	bool flag = false;
	bool except = false;
	std::thread t([&]() {
		try {
			(*ref).method();
		}
		catch (...) {
			except = true;
		}
		flag = ref.dispose();
		});
	t.join();
	ASSERT_TRUE(!flag);
	ASSERT_TRUE(except);
	ref.dispose();
}

CASE(TestDump1) {
	const int max_n = 100;
	std::list<ObjectRef<Dummy>> list;
	for (int i = 0; i < max_n; i++) {
		list.push_back(spawn_object_multithread(Dummy));
	}
	std::stringstream ss;
	dump(ss);
	int n = 0;
	std::string str;
	while (!ss.eof()) {
		n++;
		std::getline(ss, str);
	}
	ASSERT_TRUE(max_n == get_alive_object_count());
#ifdef OBJECT_SYSTEM_DEBUG
	ASSERT_TRUE(n-1 == max_n);
#endif
	for (auto o : list) {
		o.dispose();
	}
}

class BaseClass {
public:
	virtual ~BaseClass() {}
};

class ChildClass : public BaseClass {
public:
	virtual ~ChildClass() {}
};

CASE(TestDynamicCast1) {
	auto ref = spawn_object(ChildClass);
	auto base_ref = dynamic_cast_ref<BaseClass>(ref);
	base_ref.dispose();
	ASSERT_TRUE(!base_ref);
	ASSERT_TRUE(!ref);
}

CASE(TestDynamicCast2) {
	auto ref = spawn_object(ChildClass);
	auto base_ref = dynamic_cast_ref<BaseClass>(std::move(ref));
	base_ref.dispose();
	ASSERT_TRUE(!base_ref);
	ASSERT_TRUE(!ref);
}

CASE(TestStaticCast1) {
	auto ref = spawn_object(ChildClass);
	auto base_ref = static_cast_ref<BaseClass>(ref);
	base_ref.dispose();
	ASSERT_TRUE(!base_ref);
	ASSERT_TRUE(!ref);
}

CASE(TestStaticCast2) {
	auto ref = spawn_object(ChildClass);
	auto base_ref = static_cast_ref<BaseClass>(std::move(ref));
	base_ref.dispose();
	ASSERT_TRUE(!base_ref);
	ASSERT_TRUE(!ref);
}

CASE(TestConstCast1) {
	const auto ref = spawn_object(ChildClass);
	auto base_ref = const_cast_ref<ChildClass>(ref);
	base_ref.dispose();
	ASSERT_TRUE(!base_ref);
	ASSERT_TRUE(!ref);
}

CASE(TestConstCast2) {
	auto ref = spawn_object(ChildClass);
	auto base_ref = const_cast_ref<ChildClass>(std::move(ref));
	base_ref.dispose();
	ASSERT_TRUE(!base_ref);
	ASSERT_TRUE(!ref);
}

CASE(TestContainer1) {
	class MyObject {
	public:
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() const { return 0; }
	};

	using OtherRef = ObjectRef<MyObject>;

	class ComObject {
	private:
		OtherRef obj_ref_;
	public:
		ComObject(const OtherRef& other_obj) : obj_ref_(other_obj) {
		}
		virtual ~ComObject() {
			obj_ref_.dispose();
		}
		const OtherRef& getOther() const {
			return obj_ref_;
		}
	};

	auto com_ref = spawn_object(ComObject, spawn_object(MyObject, "hello", 1));
	com_ref->getOther()->method();
	com_ref.dispose();
}

CASE(TestScopeRef1) {
	class MyObject {
	public:
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() const { return 0; }
	};

	UniqueRef<MyObject> scope_ref(spawn_object(MyObject, "hello", 1));
}

CASE(TestScopeRef2) {
	class MyObject {
	public:
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() const { return 0; }
	};

	UniqueRef<MyObject> scope_ref(spawn_object(MyObject, "hello", 1));
	scope_ref.get()->method();
}

CASE(TestScopeRef3) {
	class MyObject {
	public:
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() const { return 0; }
	};

	const UniqueRef<MyObject> scope_ref(spawn_object(MyObject, "hello", 1));
	scope_ref.get()->method();
}

CASE(TestScopeRef4) {
	class MyObject {
	public:
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() const { return 0; }
	};
	UniqueRef<MyObject> scope_ref = spawn_object(MyObject, "hello", 1);
	scope_ref.get()->method();
}

CASE(TestScopeRef5) {
	class MyObject {
	public:
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() const { return 0; }
	};
	const UniqueRef<MyObject> scope_ref = spawn_object(MyObject, "hello", 1);
	scope_ref.get()->method();
}

CASE(TestScopeRef6) {
	class MyObject {
	public:
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() const { return 0; }
	};

	using ScopeOtherRef = UniqueRef<MyObject>;
	using OtherRef = ObjectRef<MyObject>;

	class ComObject {
	private:
		ScopeOtherRef obj_ref_;
	public:
		ComObject(const OtherRef& other_ref) : obj_ref_(other_ref) {
		}
		virtual ~ComObject() {
		}
		const OtherRef& getOther() const {
			return obj_ref_.get();
		}
	};
	auto other_ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<ComObject> scope_ref(spawn_object(ComObject, other_ref));
	scope_ref.get()->getOther()->method();
}

CASE(TestScopeRef7) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};

	auto unique_ref1 = UniqueRef<MyObject>(Object::ThreadMode::mode_single);
	auto unique_ref2 = UniqueRef<MyObject>(Object::ThreadMode::mode_multi, "hello", 1);
}

CASE(TestScopeRef8) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	auto unique_ref = UniqueRef<MyObject>(ref);
	bool flag = false;
	ref.dispose();
	try {
		unique_ref.get()->method();
	} catch (...) {
		flag = true;
	}
	ASSERT_TRUE(flag);
}

CASE(TestScopeRef9) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	auto unique_ref = UniqueRef<MyObject>(ref);
	ref.dispose();
	ASSERT_TRUE(!unique_ref);
}

CASE(TestScopeRef10) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	const auto unique_ref = UniqueRef<MyObject>(ref);
	ref.dispose();
	ASSERT_TRUE(!unique_ref);
}

CASE(TestScopeRef11) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto unique_ref = UniqueRef<MyObject>(Object::ThreadMode::mode_single, "hello", 1);
	UniqueRef<MyObject> unique_ref1(unique_ref);
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef12) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto unique_ref = UniqueRef<MyObject>(Object::ThreadMode::mode_single, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1 = unique_ref;
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef13) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto unique_ref = UniqueRef<MyObject>(Object::ThreadMode::mode_single, "hello", 1);
	UniqueRef<MyObject> unique_ref1(std::move(unique_ref));
	ASSERT_TRUE(!unique_ref);
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef14) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto unique_ref = UniqueRef<MyObject>(Object::ThreadMode::mode_single, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1 = std::move(unique_ref);
	ASSERT_TRUE(!unique_ref);
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef15) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1 = std::move(ref);
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef16) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1.reset(ref);
	unique_ref1.reset(ref);
	unique_ref1.reset(std::move(ref));
	unique_ref1 = unique_ref1;
	unique_ref1 = std::move(unique_ref1);
	ASSERT_TRUE(ref);
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef17) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1.reset(std::move(ref));
	unique_ref1 = ref;
	unique_ref1 = std::move(ref);
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef18) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1 = ref;
	unique_ref1 = spawn_object(MyObject, "hello", 1);
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef19) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1 = ref;
	UniqueRef<MyObject> unique_ref2 = spawn_object(MyObject, "hello", 1);
	unique_ref1 = unique_ref2;
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(unique_ref1);
	ASSERT_TRUE(unique_ref2);
}

CASE(TestScopeRef20) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1 = ref;
	UniqueRef<MyObject> unique_ref2 = spawn_object(MyObject, "hello", 1);
	unique_ref1 = std::move(unique_ref2);
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(unique_ref1);
	ASSERT_TRUE(!unique_ref2);
}

CASE(TestScopeRef21) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1 = ref;
	auto ref1 = spawn_object(MyObject, "hello", 1);
	unique_ref1.reset(ref1);
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(ref1);
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef22) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1 = ref;
	auto ref1 = spawn_object(MyObject, "hello", 1);
	unique_ref1.reset(std::move(ref1));
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(!ref1);
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef23) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1 = ref;
	auto ref1 = spawn_object(MyObject, "hello", 1);
	unique_ref1 = ref1;
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(ref1);
	ASSERT_TRUE(unique_ref1);
}

CASE(TestScopeRef24) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};
	auto ref = spawn_object(MyObject, "hello", 1);
	UniqueRef<MyObject> unique_ref1;
	unique_ref1 = ref;
	unique_ref1.reset();
	ASSERT_TRUE(!ref);
	ASSERT_TRUE(!unique_ref1);
}

CASE(TestKeySet1) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};

	object_map<MyObject, std::string> om;
	auto ref1 = spawn_object(MyObject);
	auto ref2 = spawn_object(MyObject);
	om[ref1] = "a";
	om[ref2] = "b";
	for (auto& i : om) {
		i.first.dispose();
	}
}

CASE(TestKeySet2) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};

	object_unordered_map<MyObject, std::string> om;
	auto ref1 = spawn_object(MyObject);
	auto ref2 = spawn_object(MyObject);
	om[ref1] = "a";
	om[ref2] = "b";
	for (auto& i : om) {
		i.first.dispose();
	}
}

CASE(TestKeySet3) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};

	object_set<MyObject> om;
	om.insert(spawn_object(MyObject));
	om.insert(spawn_object(MyObject));
	for (auto& i : om) {
		i.dispose();
	}
}

CASE(TestKeySet4) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};

	object_unordered_set<MyObject> om;
	om.insert(spawn_object(MyObject));
	om.insert(spawn_object(MyObject));
	for (auto& i : om) {
		i.dispose();
	}
}

CASE(TestValue1) {
	class MyObject {
	public:
		MyObject() {}
		MyObject(const std::string& a, int b) {
		}
		virtual ~MyObject() {}
		int method() { return 0; }
	};

	std::unordered_map<std::string, UniqueRef<MyObject>> om;
	auto ref1 = spawn_object(MyObject);
	auto ref2 = spawn_object(MyObject);
	om["a"] = ref1;
	om["b"] = ref2;
}

CASE(TestUniqueKey1) {
	unique_object_set<Dummy> set;
	for (int i = 0; i < 100; i++) {
		set.insert(UniqueRef<Dummy>());
	}
}

CASE(TestUniqueKey2) {
	unique_object_unordered_set<Dummy> set;
	for (int i = 0; i < 100; i++) {
		set.insert(UniqueRef<Dummy>());
	}
}

CASE(TestUniqueKey3) {
	unique_object_map<Dummy, int> map;
	for (int i = 0; i < 100; i++) {
		map[UniqueRef<Dummy>()] = 1;
	}
}

CASE(TestUniqueKey4) {
	unique_object_unordered_map<Dummy, int> map;
	for (int i = 0; i < 100; i++) {
		map[UniqueRef<Dummy>()] = 1;
	}
}

CASE(TestCleanup) {
	ASSERT_TRUE(0 == get_alive_object_count());
	ASSERT_TRUE(0 == get_dispose_object_count());
}

FIXTURE_END(test_object_system)
